Plasma only samples
import pandas as pd
samples = pd.read_csv('data/sample_sheet.csv')
# must be Healthy Control Study and must pass MiSeq QC
samples = samples.loc[(samples['Study'] == 'Healthy Controls') & (samples['MISEQ.QC.PASS'] == 'PASS')]
samples = samples.set_index('MT.Unique.ID').sort_values(by=['Participant.ID', 'Source'])
# capitalize & strip whitespace for consistency
for column in ['Gender', 'Race', 'Source']:
samples[column] = samples[column].str.capitalize()
samples[column] = samples[column].str.strip()
# use correct ontology terms
race_ontology = {'Asian': 'Asian',
'Black or african american': 'African_American',
'Mixed/asian & white': 'Multiracial',
'Mixed/asian &black': 'Multiracial',
'Mixed/black, white, asian': 'Multiracial',
'Native hawiian or other pacific islander': 'Pacific_Islander',
'Pacific islander': 'Pacific_Islander',
'White': 'White'}
for id in samples.index:
race = samples.at[id, 'Race']
samples.at[id, 'Race'] = race_ontology[race] if race in race_ontology else 'Multiracial'
# get only the plasma samples
mir_counts = pd.read_csv("data/get_canonical/canon_mir_counts.csv", index_col=0)
samples.index = pd.Index(['X' + str(row) for row in samples.index])
plasma_samples = samples.loc[samples['Source'] == "Plasma"]
plasma_mir_counts = mir_counts[plasma_samples.index]
plasma_samples.to_csv('data/plasma_samples.csv')
plasma_mir_counts.to_csv('data/plasma_mir_counts.csv')
Load the sample data and miRNA counts
samples <- read.csv('data/plasma_samples.csv')
counts <- read.csv('data/plasma_mir_counts.csv')
samples <- subset(samples, Library.Generation.Set != "SetRecheck" ) # exclude SetRecheck samples
samples <- samples[samples$X != "X11" & samples$X != "X92",] # remove outliers
samples$Participant.ID <- factor(samples$Participant.ID) # ID is categorical, not numerical
rownames(counts) = counts$X
rownames(samples) = samples$X
counts$X <- NULL # remove extra column
samples$X <- NULL
counts <- counts[,unique(rownames(samples))]
head(samples)
head(counts)
Filter
library(edgeR)
design <- model.matrix(~0+Race+Gender+Age, samples)
dge = DGEList(counts = counts, samples = samples)
# require miRNAs to have CPM > 1 in at least 2 samples
countsPerMillion <- edgeR::cpm(dge)
countCheck <- countsPerMillion > 1
head(countCheck)
X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X25
hsa-let-7a-3p TRUE TRUE FALSE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X26 X27 X28 X29 X30 X31 X32 X33 X34 X35 X36 X37 X38 X39 X40 X41 X42 X43 X44 X45 X46 X48
hsa-let-7a-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X49 X50 X52 X53 X54 X55 X56 X57 X58 X59 X60 X61 X62 X63 X64 X65 X66 X67 X68 X69 X76 X77
hsa-let-7a-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X79 X80 X81 X82 X83 X84 X85 X86 X87 X88 X89 X90 X91 X93 X96 X99 X100 X102 X103 X104 X105 X106
hsa-let-7a-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
keep <- which(rowSums(countCheck) >= 2)
dge <- dge[keep,]
Explore variance


hist(reads_sce$total_counts, breaks=100) # counts per sample
hist(reads_sce$total_features, breaks=100) # counts per miRNA
plotQC(reads_sce, type = "highest-expression")

plotQC(reads_sce, type="explanatory-variables", variables=c("Index", "Participant.ID", "Collection.Date", "Library.Generation.Set", "MiSeq.QC.Run", 'total_features', "Age", "Race", "Gender"))

Examine sources of variation
plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Library.Generation.Set", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Race", shape_by="Gender", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "logcounts", variable = var)
) + ggtitle(var)
}








Normalize & Remove unwanted sources of variation
library(RUVSeq)
library(ggplot2)
library(mvoutlier)
dge <- calcNormFactors(dge)
dge <- estimateGLMCommonDisp(dge, design)
dge <- estimateGLMTagwiseDisp(dge, design)
fit <- glmFit(dge, design)
res <- residuals(fit, type="deviance")
set <- newSeqExpressionSet(dge$counts, phenoData=dge$samples)
ruvr_sets <- list()
for(k in 1:5) {
ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res)
assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])$normalizedCounts) / colSums(assayData(ruvr_sets[[k]])$normalizedCounts) * 1e6) + 1)
}
for(n in assayNames(reads_sce)) {
print(
plotPCA(
reads_sce,
colour_by = "Library.Generation.Set",
size_by = "total_features",
exprs_values = n
) + ggtitle(paste("Batch",n))
)
print(
plotPCA(
reads_sce,
colour_by = "Race",
shape_by = "Gender",
size_by = "total_features",
exprs_values = n
) + ggtitle(paste("Demographics", n))
)
}
non-plotting arguments like 'exprs_values' should go in 'run_args'
















Detect outliers
reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
outliers <- colnames(reads_sce)[reads_sce$outlier]
head(outliers)
character(0)
Examine sources of variance after removing unwanted variation
for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr k= 2", variable = var)
) + ggtitle(var)
}








Visualize top highly-expressed miRNAs male-vs-female
library(RColorBrewer)
library(reshape2)
# Ryan's code, modified
norm.expr.matr <- exprs(reads_sce)
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.male.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Gender=="Male"]))
mean.expr.female.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Gender=="Female"]))
top_N <- 20
top.miRs <- row.names(norm.expr.matr)[mean.expr.male.rank <= top_N | mean.expr.female.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Gender <- ""
for (row_num in 1:nrow(norm.expr.melt)){ # Pull the Gender values from the column metadata.
mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
norm.expr.melt[row_num, "Gender"] <- as.character(samples[mt_unique_id,"Gender"])
}
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Gender)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Gender)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.x = element_blank()) + coord_flip()

Visualize top highly-expressed miRNAs by Race
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.afr.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="African_American"]))
mean.expr.asn.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="Asian"]))
mean.expr.wht.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="White"]))
top_N <- 20
top.miRs <- row.names(norm.expr.matr)[mean.expr.afr.rank <= top_N | mean.expr.asn.rank <= top_N | mean.expr.wht.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Race <- ""
for (row_num in 1:nrow(norm.expr.melt)){ # Pull the Gender values from the column metadata.
mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
norm.expr.melt[row_num, "Race"] <- as.character(samples[mt_unique_id,"Race"])
}
norm.expr.melt <- norm.expr.melt[norm.expr.melt$Race %in% c("African_American", "Asian", "White"),]
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Race)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Race)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.x = element_blank()) + coord_flip()

Heatmap of most highly expressed miRNAs
library(pheatmap)
ruvr2 <- ruvr_sets[[2]]
norm.expr.matr <- norm.expr.matr[order(rowMeans(norm.expr.matr), decreasing=TRUE),]
Demographics <- pData(ruvr2)[,c("Race", "Gender")]
annotations <- as.data.frame(Demographics)
rownames(annotations) <- rownames(pData(ruvr2))
pheatmap(norm.expr.matr[0:50,], cluster_rows=FALSE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Top 50 Expressed miRNAs")

DE analysis with EdgeR
# based on Ryan's code, pass RUVSeq-corrected data to edgeR
ruvr2 <- ruvr_sets[[2]]
design <- model.matrix(~ 0 + Race + Gender + Age + W_1 + W_2, pData(ruvr2))
dge <- estimateDisp(dge, design = design, tagwise = TRUE, robust = TRUE)
fit <- glmFit(dge, design)
Contrast gender
lrt <- glmLRT(fit, coef = "GenderMale")
results <- data.frame(topTags(lrt, n=Inf, sort.by="PValue", p.value=0.05))
sig_miRs = list()
logFC_threshold <- 1
sig_miRs[[1]] <- rownames(results[results$PValue < 0.05 & abs(results$logFC) > logFC_threshold,]) # filter by p-value and logFC
sig_miRs[[2]] <- rownames(results[results$PValue < 0.05,])
# heatmaps of significant DE miRNAs
Gender <- pData(ruvr2)[,c("Gender")]
annotations <- as.data.frame(Gender)
rownames(annotations) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs) {
pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Differentially Expressed miRNAs")
}


Contrast race
lrt <- glmLRT(fit, contrast=makeContrasts(asn=(RaceAsian-(RaceAfrican_American+RaceWhite)/2),
afr=(RaceAfrican_American-(RaceAsian+RaceWhite)/2),
wht=(RaceWhite-(RaceAfrican_American+RaceAsian)/2), levels=design))
results <- data.frame(topTags(lrt, n=Inf, sort.by="PValue", p.value=0.05))
sig_miRs = list()
logFC_threshold <- 1
sig_miRs[[1]] <- rownames(results[results$PValue < 0.05 & abs(results$logFC.asn) > logFC_threshold | abs(results$logFC.afr) > logFC_threshold | abs(results$logFC.wht) > logFC_threshold,]) # filter by p-value and logFC
sig_miRs[[2]] <- rownames(results[results$PValue < 0.05,])
# heatmaps of significant DE miRNAs
Demographics <- pData(ruvr2)[,c("Race", "Gender")]
annotations <- as.data.frame(Demographics)
rownames(annotations) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs) {
pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Differentially Expressed miRNAs")
}


Create a PCA plot showing Age x Gender
plotPCA(
reads_sce,
colour_by = "Age",
shape_by = "Gender",
size_by = "total_features",
exprs_values = "RUVr k= 2"
) + ggtitle("RUVSeq-Normalized Expression (k=2)")
non-plotting arguments like 'exprs_values' should go in 'run_args'

save.image("diff_expr_plasma_only.RData")
LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIHBsYXNtYSBzYW1wbGVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCiMgUGxhc21hIG9ubHkgc2FtcGxlcwpgYGB7cHl0aG9uM30KaW1wb3J0IHBhbmRhcyBhcyBwZApzYW1wbGVzID0gcGQucmVhZF9jc3YoJ2RhdGEvc2FtcGxlX3NoZWV0LmNzdicpCiMgbXVzdCBiZSBIZWFsdGh5IENvbnRyb2wgU3R1ZHkgYW5kIG11c3QgcGFzcyBNaVNlcSBRQwpzYW1wbGVzID0gc2FtcGxlcy5sb2NbKHNhbXBsZXNbJ1N0dWR5J10gPT0gJ0hlYWx0aHkgQ29udHJvbHMnKSAmIChzYW1wbGVzWydNSVNFUS5RQy5QQVNTJ10gPT0gJ1BBU1MnKV0gIApzYW1wbGVzID0gc2FtcGxlcy5zZXRfaW5kZXgoJ01ULlVuaXF1ZS5JRCcpLnNvcnRfdmFsdWVzKGJ5PVsnUGFydGljaXBhbnQuSUQnLCAnU291cmNlJ10pCiMgY2FwaXRhbGl6ZSAmIHN0cmlwIHdoaXRlc3BhY2UgZm9yIGNvbnNpc3RlbmN5CmZvciBjb2x1bW4gaW4gWydHZW5kZXInLCAnUmFjZScsICdTb3VyY2UnXToKICAgIHNhbXBsZXNbY29sdW1uXSA9IHNhbXBsZXNbY29sdW1uXS5zdHIuY2FwaXRhbGl6ZSgpCiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLnN0cmlwKCkKIyB1c2UgY29ycmVjdCBvbnRvbG9neSB0ZXJtcwpyYWNlX29udG9sb2d5ID0geydBc2lhbic6ICdBc2lhbicsCiAnQmxhY2sgb3IgYWZyaWNhbiBhbWVyaWNhbic6ICdBZnJpY2FuX0FtZXJpY2FuJywKICdNaXhlZC9hc2lhbiAmIHdoaXRlJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9hc2lhbiAmYmxhY2snOiAnTXVsdGlyYWNpYWwnLAogJ01peGVkL2JsYWNrLCB3aGl0ZSwgYXNpYW4nOiAnTXVsdGlyYWNpYWwnLAogJ05hdGl2ZSBoYXdpaWFuIG9yIG90aGVyIHBhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnUGFjaWZpYyBpc2xhbmRlcic6ICdQYWNpZmljX0lzbGFuZGVyJywKICdXaGl0ZSc6ICdXaGl0ZSd9CmZvciBpZCBpbiBzYW1wbGVzLmluZGV4OgogICAgcmFjZSA9IHNhbXBsZXMuYXRbaWQsICdSYWNlJ10KICAgIHNhbXBsZXMuYXRbaWQsICdSYWNlJ10gPSByYWNlX29udG9sb2d5W3JhY2VdIGlmIHJhY2UgaW4gcmFjZV9vbnRvbG9neSBlbHNlICdNdWx0aXJhY2lhbCcKIyBnZXQgb25seSB0aGUgcGxhc21hIHNhbXBsZXMKbWlyX2NvdW50cyA9IHBkLnJlYWRfY3N2KCJkYXRhL2dldF9jYW5vbmljYWwvY2Fub25fbWlyX2NvdW50cy5jc3YiLCBpbmRleF9jb2w9MCkKc2FtcGxlcy5pbmRleCA9IHBkLkluZGV4KFsnWCcgKyBzdHIocm93KSBmb3Igcm93IGluIHNhbXBsZXMuaW5kZXhdKQpwbGFzbWFfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09ICJQbGFzbWEiXQpwbGFzbWFfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbcGxhc21hX3NhbXBsZXMuaW5kZXhdCnBsYXNtYV9zYW1wbGVzLnRvX2NzdignZGF0YS9wbGFzbWFfc2FtcGxlcy5jc3YnKQpwbGFzbWFfbWlyX2NvdW50cy50b19jc3YoJ2RhdGEvcGxhc21hX21pcl9jb3VudHMuY3N2JykKYGBgCgojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL3BsYXNtYV9zYW1wbGVzLmNzdicpCmNvdW50cyA8LSByZWFkLmNzdignZGF0YS9wbGFzbWFfbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzIDwtIHN1YnNldChzYW1wbGVzLCBMaWJyYXJ5LkdlbmVyYXRpb24uU2V0ICE9ICJTZXRSZWNoZWNrIiApICAjIGV4Y2x1ZGUgU2V0UmVjaGVjayBzYW1wbGVzCnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFggIT0gIlgxMSIgJiBzYW1wbGVzJFggIT0gIlg5MiIsXSAjIHJlbW92ZSBvdXRsaWVycwpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApyb3duYW1lcyhjb3VudHMpID0gY291bnRzJFgKcm93bmFtZXMoc2FtcGxlcykgPSBzYW1wbGVzJFgKY291bnRzJFggPC0gTlVMTCAgIyByZW1vdmUgZXh0cmEgY29sdW1uCnNhbXBsZXMkWCA8LSBOVUxMCmNvdW50cyA8LSBjb3VudHNbLHVuaXF1ZShyb3duYW1lcyhzYW1wbGVzKSldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAoKIyMgRmlsdGVyCmBgYHtyfQpsaWJyYXJ5KGVkZ2VSKQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4wK1JhY2UrR2VuZGVyK0FnZSwgc2FtcGxlcykKZGdlID0gREdFTGlzdChjb3VudHMgPSBjb3VudHMsIHNhbXBsZXMgPSBzYW1wbGVzKQojIHJlcXVpcmUgbWlSTkFzIHRvIGhhdmUgQ1BNID4gMSBpbiBhdCBsZWFzdCAyIHNhbXBsZXMKY291bnRzUGVyTWlsbGlvbiA8LSBlZGdlUjo6Y3BtKGRnZSkKY291bnRDaGVjayA8LSBjb3VudHNQZXJNaWxsaW9uID4gMQpoZWFkKGNvdW50Q2hlY2spCmtlZXAgPC0gd2hpY2gocm93U3Vtcyhjb3VudENoZWNrKSA+PSAyKSAKZGdlIDwtIGRnZVtrZWVwLF0KYGBgCiMjIEV4cGxvcmUgdmFyaWFuY2UKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NhdGVyKQpyZWFkc19zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWRnZSRjb3VudHMpLCAgY29sRGF0YT1kZ2Ukc2FtcGxlcykKIyByZW1vdmUgdW5leHByZXNzZWQgbWlSTkFzCmtlZXBfZmVhdHVyZSA8LSByb3dTdW1zKGNvdW50cyhyZWFkc19zY2UpID4gMCkgPiAwCnJlYWRzX3NjZSA8LSByZWFkc19zY2Vba2VlcF9mZWF0dXJlLCBdCnJlYWRzX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocmVhZHNfc2NlKQojIGxvZyB0cmFuc2Zvcm0KY3BtKHJlYWRzX3NjZSkgPC0gY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkKcmVhZHNfc2NlIDwtIG5vcm1hbGl6ZShyZWFkc19zY2UpCmxvZ2NvdW50cyhyZWFkc19zY2UpIDwtIGxvZzIoY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkgKyAxKQojIHZpc3VhbGl6ZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9jb3VudHMsIGJyZWFrcz0xMDApICAjIGNvdW50cyBwZXIgc2FtcGxlCmhpc3QocmVhZHNfc2NlJHRvdGFsX2ZlYXR1cmVzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIG1pUk5BCnBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikKcGxvdFFDKHJlYWRzX3NjZSwgdHlwZT0iZXhwbGFuYXRvcnktdmFyaWFibGVzIiwgdmFyaWFibGVzPWMoIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIkNvbGxlY3Rpb24uRGF0ZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIk1pU2VxLlFDLlJ1biIsICd0b3RhbF9mZWF0dXJlcycsICJBZ2UiLCAiUmFjZSIsICJHZW5kZXIiKSkKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYXRpb24KYGBge3J9CnBsb3RQQ0EocmVhZHNfc2NlLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgY29sb3VyX2J5ID0gIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiUmFjZSIsIHNoYXBlX2J5PSJHZW5kZXIiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKZm9yICh2YXIgaW4gYygidG90YWxfZmVhdHVyZXMiLCAiQWdlIiwgIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCAiSW5kZXgiLCAiUGFydGljaXBhbnQuSUQiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkJhdGNoIixuKSkKICApCiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiUmFjZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkRlbW9ncmFwaGljcyIsIG4pKSAgICAgICAgCiAgKQp9CmBgYAoKIyMgRGV0ZWN0IG91dGxpZXJzCmBgYHtyfQpyZWFkc19zY2UgPC0gcnVuUENBKHJlYWRzX3NjZSwgdXNlX2NvbGRhdGEgPSBUUlVFLCBkZXRlY3Rfb3V0bGllcnMgPSBUUlVFKQpvdXRsaWVycyA8LSBjb2xuYW1lcyhyZWFkc19zY2UpW3JlYWRzX3NjZSRvdXRsaWVyXQpoZWFkKG91dGxpZXJzKQpgYGAKCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlJhY2UiLCAiR2VuZGVyIiwgIkNvbGxlY3Rpb24uRGF0ZSIpKSB7CiAgcHJpbnQoCiAgICBwbG90UUMocmVhZHNfc2NlLCB0eXBlID0gImZpbmQtcGNzIiwgZXhwcnNfdmFsdWVzID0gIlJVVnIgaz0gMiIsIHZhcmlhYmxlID0gdmFyKQogICAgKSAgKyBnZ3RpdGxlKHZhcikgCn0KYGBgCiMjIFZpc3VhbGl6ZSB0b3AgaGlnaGx5LWV4cHJlc3NlZCBtaVJOQXMgbWFsZS12cy1mZW1hbGUKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHJlc2hhcGUyKQojIFJ5YW4ncyBjb2RlLCBtb2RpZmllZApub3JtLmV4cHIubWF0ciA8LSBleHBycyhyZWFkc19zY2UpCiMgUmFuayB0aGUgbWVhbiBleHByZXNzaW9uIHZhbHVlcyBmb3IgcGxhc21hL3NlcnVtIG1pUnMuIEhpZ2hlc3QgZXhwcmVzc2lvbiA9IDEKbWVhbi5leHByLm1hbGUucmFuayA8LSByYW5rKC0xKnJvd01lYW5zKG5vcm0uZXhwci5tYXRyWywgY29sRGF0YShyZWFkc19zY2UpJEdlbmRlcj09Ik1hbGUiXSkpCm1lYW4uZXhwci5mZW1hbGUucmFuayA8LSByYW5rKC0xKnJvd01lYW5zKG5vcm0uZXhwci5tYXRyWywgY29sRGF0YShyZWFkc19zY2UpJEdlbmRlcj09IkZlbWFsZSJdKSkKdG9wX04gPC0gMjAKdG9wLm1pUnMgPC0gcm93Lm5hbWVzKG5vcm0uZXhwci5tYXRyKVttZWFuLmV4cHIubWFsZS5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLmZlbWFsZS5yYW5rIDw9IHRvcF9OXSAjIGdldCB0aGUgbmFtZXMgb2YgdGhlIHRvcCBtaVJzIGluIHBsYXNtYSBvciBzZXJ1bQpub3JtLmV4cHIudG9wIDwtIG5vcm0uZXhwci5tYXRyW3RvcC5taVJzLCBdICMgR2V0IHRoZSBleHByZXNzaW9uIG1hdHJpeCBmb3IgdGhlIHRvcCBtSVJzCm5vcm0uZXhwci5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KG5vcm0uZXhwci50b3ApICMgQ29udmVydCB5b3VyIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXggdG8gYSAzIGNvbHVtbiBkYXRhLmZyYW1lIChyb3cubmFtZSwgY29sLm5hbWUsIGV4cHJlc3Npb24gdmFsdWUpLiBNZWx0IGlzIGluIHRoZSBkcHlsciBwYWNrYWdlLCBJIGJlbGlldmUuCmNvbG5hbWVzKG5vcm0uZXhwci5tZWx0KSA8LSBjKCJtaVIuSUQiLCAiTVQuVW5pcXVlLklEIiwgIm5vcm0uZXhwciIpICMganVzdCBzbyBpdCdzIGVhc2llciBmb3IgbWUgdG8gdGVsbCB5b3Ugd2hpY2ggY29sdW1ucyBJJ20gdXNpbmcuCm5vcm0uZXhwci5tZWx0JEdlbmRlciA8LSAiIgpmb3IgKHJvd19udW0gaW4gMTpucm93KG5vcm0uZXhwci5tZWx0KSl7ICAjIFB1bGwgdGhlIEdlbmRlciB2YWx1ZXMgZnJvbSB0aGUgY29sdW1uIG1ldGFkYXRhLgogIG10X3VuaXF1ZV9pZCA8LSBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCBdJE1ULlVuaXF1ZS5JRAogIG5vcm0uZXhwci5tZWx0W3Jvd19udW0sICJHZW5kZXIiXSA8LSBhcy5jaGFyYWN0ZXIoc2FtcGxlc1ttdF91bmlxdWVfaWQsIkdlbmRlciJdKQp9CiMgVGhpcyB3b3VsZCBiZSBhIHNpbXBsZSBib3hwbG90IHdpdGggcGxhc21hL3NlcnVtIHNpZGUtYnktc2lkZS4gT3ZlcmxheWluZyB0aGUgYm94ZXMgb3ZlciBwb2ludHMgdGFrZXMgYSBsaXR0bGUgbW9yZSB0d2Vha2luZyB0byBnZXQgdGhlIGRvZGdlL3dpZHRoIHJpZ2h0LCBidXQgaXQncyBkb2FibGUuCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPW1lZGlhbiksIHk9bm9ybS5leHByLCBmaWxsPUdlbmRlcikpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKZ2dwbG90KG5vcm0uZXhwci5tZWx0LCBhZXMoeD1yZW9yZGVyKG1pUi5JRCwgbm9ybS5leHByLCBGVU49bWVkaWFuKSwgeT1ub3JtLmV4cHIsIGZpbGw9R2VuZGVyKSkgKyBnZW9tX2JveHBsb3QocG9zPSJkb2RnZSIsIG91dGxpZXIuc2l6ZT0wLjUpICsgCiAgZ2d0aXRsZSgiVG9wIDIwIEV4cHJlc3NlZCBtaVJOQXMiKSArIHlsYWIoIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIpICsgeGxhYigibWlSTkEgSUQiKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShzaXplID0gMC41LCBsaW5ldHlwZSA9ICdzb2xpZCcsIGNvbG91ciA9ICJncmV5IiksIAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyBjb29yZF9mbGlwKCkKYGBgCiMjIFZpc3VhbGl6ZSB0b3AgaGlnaGx5LWV4cHJlc3NlZCBtaVJOQXMgYnkgUmFjZQpgYGB7cn0KIyBSYW5rIHRoZSBtZWFuIGV4cHJlc3Npb24gdmFsdWVzIGZvciBwbGFzbWEvc2VydW0gbWlScy4gSGlnaGVzdCBleHByZXNzaW9uID0gMQptZWFuLmV4cHIuYWZyLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRSYWNlPT0iQWZyaWNhbl9BbWVyaWNhbiJdKSkKbWVhbi5leHByLmFzbi5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkUmFjZT09IkFzaWFuIl0pKQptZWFuLmV4cHIud2h0LnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRSYWNlPT0iV2hpdGUiXSkpCnRvcF9OIDwtIDIwCnRvcC5taVJzIDwtIHJvdy5uYW1lcyhub3JtLmV4cHIubWF0cilbbWVhbi5leHByLmFmci5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLmFzbi5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLndodC5yYW5rIDw9IHRvcF9OXSAjIGdldCB0aGUgbmFtZXMgb2YgdGhlIHRvcCBtaVJzIGluIHBsYXNtYSBvciBzZXJ1bQpub3JtLmV4cHIudG9wIDwtIG5vcm0uZXhwci5tYXRyW3RvcC5taVJzLCBdICMgR2V0IHRoZSBleHByZXNzaW9uIG1hdHJpeCBmb3IgdGhlIHRvcCBtSVJzCm5vcm0uZXhwci5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KG5vcm0uZXhwci50b3ApICMgQ29udmVydCB5b3VyIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXggdG8gYSAzIGNvbHVtbiBkYXRhLmZyYW1lIChyb3cubmFtZSwgY29sLm5hbWUsIGV4cHJlc3Npb24gdmFsdWUpLiBNZWx0IGlzIGluIHRoZSBkcHlsciBwYWNrYWdlLCBJIGJlbGlldmUuCmNvbG5hbWVzKG5vcm0uZXhwci5tZWx0KSA8LSBjKCJtaVIuSUQiLCAiTVQuVW5pcXVlLklEIiwgIm5vcm0uZXhwciIpICMganVzdCBzbyBpdCdzIGVhc2llciBmb3IgbWUgdG8gdGVsbCB5b3Ugd2hpY2ggY29sdW1ucyBJJ20gdXNpbmcuCm5vcm0uZXhwci5tZWx0JFJhY2UgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBHZW5kZXIgdmFsdWVzIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiUmFjZSJdIDwtIGFzLmNoYXJhY3RlcihzYW1wbGVzW210X3VuaXF1ZV9pZCwiUmFjZSJdKQp9Cm5vcm0uZXhwci5tZWx0IDwtIG5vcm0uZXhwci5tZWx0W25vcm0uZXhwci5tZWx0JFJhY2UgJWluJSBjKCJBZnJpY2FuX0FtZXJpY2FuIiwgIkFzaWFuIiwgIldoaXRlIiksXQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1SYWNlKSkgKyBnZW9tX2JveHBsb3QocG9zPSJkb2RnZSIsIG91dGxpZXIuc2l6ZT0wLjUpICsgCiAgZ2d0aXRsZSgiVG9wIDIwIEV4cHJlc3NlZCBtaVJOQXMiKSArIHlsYWIoIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIpICsgeGxhYigibWlSTkEgSUQiKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShzaXplID0gMC41LCBsaW5ldHlwZSA9ICdzb2xpZCcsIGNvbG91ciA9ICJncmV5IiksIAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1SYWNlKSkgKyBnZW9tX2JveHBsb3QocG9zPSJkb2RnZSIsIG91dGxpZXIuc2l6ZT0wLjUpICsgCiAgZ2d0aXRsZSgiVG9wIDIwIEV4cHJlc3NlZCBtaVJOQXMiKSArIHlsYWIoIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIpICsgeGxhYigibWlSTkEgSUQiKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShzaXplID0gMC41LCBsaW5ldHlwZSA9ICdzb2xpZCcsIGNvbG91ciA9ICJncmV5IiksIAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyBjb29yZF9mbGlwKCkKYGBgCgojIyBIZWF0bWFwIG9mIG1vc3QgaGlnaGx5IGV4cHJlc3NlZCBtaVJOQXMKYGBge3J9CmxpYnJhcnkocGhlYXRtYXApCnJ1dnIyIDwtIHJ1dnJfc2V0c1tbMl1dCm5vcm0uZXhwci5tYXRyIDwtIG5vcm0uZXhwci5tYXRyW29yZGVyKHJvd01lYW5zKG5vcm0uZXhwci5tYXRyKSwgZGVjcmVhc2luZz1UUlVFKSxdCkRlbW9ncmFwaGljcyA8LSBwRGF0YShydXZyMilbLGMoIlJhY2UiLCAiR2VuZGVyIildCmFubm90YXRpb25zIDwtIGFzLmRhdGEuZnJhbWUoRGVtb2dyYXBoaWNzKQpyb3duYW1lcyhhbm5vdGF0aW9ucykgPC0gcm93bmFtZXMocERhdGEocnV2cjIpKQpwaGVhdG1hcChub3JtLmV4cHIubWF0clswOjUwLF0sIGNsdXN0ZXJfcm93cz1GQUxTRSwgc2hvd19yb3duYW1lcz1UUlVFLCBjbHVzdGVyX2NvbHM9VFJVRSwgYW5ub3RhdGlvbl9jb2w9YW5ub3RhdGlvbnMsIG1haW49IlRvcCA1MCBFeHByZXNzZWQgbWlSTkFzIikKYGBgCgojIERFIGFuYWx5c2lzIHdpdGggRWRnZVIKYGBge3J9CiMgYmFzZWQgb24gUnlhbidzIGNvZGUsIHBhc3MgUlVWU2VxLWNvcnJlY3RlZCBkYXRhIHRvIGVkZ2VSCnJ1dnIyIDwtIHJ1dnJfc2V0c1tbMl1dCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofiAwICsgUmFjZSArIEdlbmRlciArIEFnZSArIFdfMSArIFdfMiwgcERhdGEocnV2cjIpKQpkZ2UgPC0gZXN0aW1hdGVEaXNwKGRnZSwgZGVzaWduID0gZGVzaWduLCB0YWd3aXNlID0gVFJVRSwgcm9idXN0ID0gVFJVRSkKZml0IDwtIGdsbUZpdChkZ2UsIGRlc2lnbikKYGBgCgojIyBDb250cmFzdCBnZW5kZXIKYGBge3J9CmxydCA8LSBnbG1MUlQoZml0LCBjb2VmID0gIkdlbmRlck1hbGUiKQpyZXN1bHRzIDwtIGRhdGEuZnJhbWUodG9wVGFncyhscnQsIG49SW5mLCBzb3J0LmJ5PSJQVmFsdWUiLCBwLnZhbHVlPTAuMDUpKQpzaWdfbWlScyA9IGxpc3QoKQpsb2dGQ190aHJlc2hvbGQgPC0gMQpzaWdfbWlSc1tbMV1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1ICYgYWJzKHJlc3VsdHMkbG9nRkMpID4gbG9nRkNfdGhyZXNob2xkLF0pICAjIGZpbHRlciBieSBwLXZhbHVlIGFuZCBsb2dGQwpzaWdfbWlSc1tbMl1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1LF0pCiMgaGVhdG1hcHMgb2Ygc2lnbmlmaWNhbnQgREUgbWlSTkFzCkdlbmRlciA8LSBwRGF0YShydXZyMilbLGMoIkdlbmRlciIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKEdlbmRlcikKcm93bmFtZXMoYW5ub3RhdGlvbnMpIDwtIHJvd25hbWVzKHBEYXRhKHJ1dnIyKSkKZm9yKG1pUl9saXN0IGluIHNpZ19taVJzKSB7CiAgcGhlYXRtYXAobm9ybS5leHByLm1hdHJbbWlSX2xpc3QsXSwgY2x1c3Rlcl9yb3dzPVRSVUUsIHNob3dfcm93bmFtZXM9VFJVRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGFubm90YXRpb25fY29sPWFubm90YXRpb25zLCBtYWluPSJEaWZmZXJlbnRpYWxseSBFeHByZXNzZWQgbWlSTkFzIikKfQpgYGAKCiMjIENvbnRyYXN0IHJhY2UKYGBge3J9CmxydCA8LSBnbG1MUlQoZml0LCBjb250cmFzdD1tYWtlQ29udHJhc3RzKGFzbj0oUmFjZUFzaWFuLShSYWNlQWZyaWNhbl9BbWVyaWNhbitSYWNlV2hpdGUpLzIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZnI9KFJhY2VBZnJpY2FuX0FtZXJpY2FuLShSYWNlQXNpYW4rUmFjZVdoaXRlKS8yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2h0PShSYWNlV2hpdGUtKFJhY2VBZnJpY2FuX0FtZXJpY2FuK1JhY2VBc2lhbikvMiksIGxldmVscz1kZXNpZ24pKQpyZXN1bHRzIDwtIGRhdGEuZnJhbWUodG9wVGFncyhscnQsIG49SW5mLCBzb3J0LmJ5PSJQVmFsdWUiLCBwLnZhbHVlPTAuMDUpKQpzaWdfbWlScyA9IGxpc3QoKQpsb2dGQ190aHJlc2hvbGQgPC0gMQpzaWdfbWlSc1tbMV1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1ICYgYWJzKHJlc3VsdHMkbG9nRkMuYXNuKSA+IGxvZ0ZDX3RocmVzaG9sZCB8IGFicyhyZXN1bHRzJGxvZ0ZDLmFmcikgPiBsb2dGQ190aHJlc2hvbGQgfCBhYnMocmVzdWx0cyRsb2dGQy53aHQpID4gbG9nRkNfdGhyZXNob2xkLF0pICAjIGZpbHRlciBieSBwLXZhbHVlIGFuZCBsb2dGQwpzaWdfbWlSc1tbMl1dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1LF0pCiMgaGVhdG1hcHMgb2Ygc2lnbmlmaWNhbnQgREUgbWlSTkFzCkRlbW9ncmFwaGljcyA8LSBwRGF0YShydXZyMilbLGMoIlJhY2UiLCAiR2VuZGVyIildCmFubm90YXRpb25zIDwtIGFzLmRhdGEuZnJhbWUoRGVtb2dyYXBoaWNzKQpyb3duYW1lcyhhbm5vdGF0aW9ucykgPC0gcm93bmFtZXMocERhdGEocnV2cjIpKQpmb3IobWlSX2xpc3QgaW4gc2lnX21pUnMpIHsKICBwaGVhdG1hcChub3JtLmV4cHIubWF0clttaVJfbGlzdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1UUlVFLCBjbHVzdGVyX2NvbHM9VFJVRSwgYW5ub3RhdGlvbl9jb2w9YW5ub3RhdGlvbnMsIG1haW49IkRpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBtaVJOQXMiKQp9CmBgYAoKIyMgQ3JlYXRlIGEgUENBIHBsb3Qgc2hvd2luZyBBZ2UgeCBHZW5kZXIKYGBge3J9CnBsb3RQQ0EoCiAgICAgICAgICAgIHJlYWRzX3NjZSwKICAgICAgICAgICAgY29sb3VyX2J5ID0gIkFnZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSAiUlVWciBrPSAyIgopICsgZ2d0aXRsZSgiUlVWU2VxLU5vcm1hbGl6ZWQgRXhwcmVzc2lvbiAoaz0yKSIpIApgYGAKCmBgYHtyfQpzYXZlLmltYWdlKCJkaWZmX2V4cHJfcGxhc21hX29ubHkuUkRhdGEiKQpgYGA=